初入JavaScript世界的我,把程式碼宣告的var移上移下,一不小心卻把它移到了console.log下面卻還是能印出undefined?那是因為變數提升這特性造成的;寫程式的時候可能習慣把function整理好統一放在程式碼的前面或後面,但為什麼它們放在哪裡都可以被呼叫呢?也是因為變數提升的關係。
創造期與執行期
瀏覽器在執行JavaScript的時候有兩個階段,第一個是創造期、第二個是執行期,像是以下這段程式碼:
1 | var food = "漢堡" |
第一階段創造期是去記憶體取得等下要執行程式碼的空間,這個階段如果要使用console.log(food)
印出「food」僅能獲得undefined
的結果,接下來進入第二階段創造期才會進行賦值,把「漢堡」賦予到「food」變數上,用console.log(food)
印出來的結果就會變成漢堡。
var的提升
先看看第一個問題,由於變數提升的特性,下面這段程式碼可以印出undefined:
1 | console.log(food) |
創造期會先得到一個food變數,這和平常一樣,但第二階段由於JS執行順序為上到下,food在被賦值之前就被印出來了,但因為依舊有「登記」到food變數,所以印出undefined。
ES6之後,出現了let、const這類更嚴謹的宣告,多了TDZ修正var的提升特性,不是說let跟const沒有提升,只是有不一樣的錯誤訊息讓開發者更好偵錯。
1 | console.log(food); |
或是
1 | console.log(food); |
加入TDZ之後就能看到這個錯誤訊息,並不是undefined(畢竟可能是var food真的沒有賦值或是其他情況導致的,當然錯誤越明確越好~)。
但有些瀏覽器版本不一樣跑出來的結果可能是Uncaught ReferenceError: food is not defined
,和沒有宣告變數的錯誤訊息一樣,所以測試的時候可能要多開幾個瀏覽器看看是不是瀏覽器版本問題(上圖是開firefox的開發者工具,下圖則是chrome)。
函式陳述式與函式表達式的提升
再來就是為什麼function放在哪裡都可以被取用,其實寫法不同也會有不一樣的結果:
函式陳述式
函式陳述式會提升到創造期就準備好了,執行期就可以直接呼叫函式:
函式表達式
函式表達式則是遵從前面提過的變數提升,因為function已經變成了變數所需要賦的值,使用var宣告的時候由於還沒有給run的值,變成了undefined,所以錯誤訊息給的是run is not a function
換成const或let的時候,一樣會有TDZ的狀態,錯誤訊息則會顯示can't access lexical declaration 'run' before initialization
執行的優先權
根據上面兩點可以發現函式陳述式的優先權是大於函式表達式的,因為陳述式會在創造期就準備好可以被呼叫,而函式表達式則多了一道關卡,function的本體在執行期才能被賦予到變數上,也就是說如果有兩個同名的function被呼叫的話最終的結果就是執行順序較後面的會覆蓋掉前面的,例如我故意把函式陳述式放在函式表達式後面,最後的結果還是函式表達式寫法的結果:
可以看見,在第二階段執行由上到下呼叫的三個hello,第一次呼叫的是創造期就準備好的函式陳述式hello,而後來呼叫的hello都是函式表達式內容覆蓋之後的結果。
結論
變數提升好好運用可以更彈性的維護專案,善用const跟let有TDZ的特性可以少走一些除錯的冤枉路。